//////////////////////////////////////////////////////////////////
// MattiseOS Kernel
// Date: 2008
// Author: Matthew Iselin
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//////////////////////////////////////////////////////////////////

// Includes
#include <mattise.h>
#include <stdint.h>
#include <mem/util.h>
#include <mem/heap.h>
#include <mem/page.h>
#include <exec/elf.h>
#include <kernio/io.h>
#include <core/portio.h>
#include <net/ne2k/ne2k.h>
#include <process/common.h>
#include <core/intrcommon.h>

// Prototypes

void ne2k_recv(uint32_t card);

// Variables

// hard-coded for testing, a real ne2k driver would have more
// information for probing
uint16_t ioaddr = 0x280;
int32_t irq = 3;

// an NE2K card
struct ne2k_card {
    unsigned short base;
    unsigned char next_packet;
	char macaddr[6];
	unsigned int ethnum; // eth<ethnum> is the ethernet interface to use
};

// all the cards in the system (we allow a max of 6)
struct ne2k_card installed_cards[6];
uint32_t nInstalled = 0;

// Functions

void ne2k_irq_handler(struct stack_layout* UNUSED(stk))
{
	// check each installed card
	uint32_t i = 0;
	dprintf("[NE2K ] irq\r\n");
	for(; i < nInstalled; i++)
	{
		uint16_t base = installed_cards[i].base;
		uint8_t ibits = inb(base + NE_ISR);

		if(ibits & 0x05)
		{
			dprintf("[NE2K ] packet received.\r\n");
			outb(base + NE_IMR, 0x3A);

			// let other interrupts happen while we deal with that packet
			// psuedo-multitasking, if you will
			asm volatile("sti");
			ne2k_recv(i);
		}

		if(ibits & 0x0A)
		{
			if(inb(base + NE_ISR) & 8)
				dprintf("[NE2K ] warning, failed transmit, TSR=%2x\n", inb(base + NE_TSR));
			else
			{
				outb(base + NE_ISR, 0x0A);
				asm volatile("sti");
				dprintf("[NE2K ] packet transmitted\n");
			}
		}

		if(ibits & 0x10)
		{
			dprintf("[NE2K ] receive buffer overflow\n");
			outb(base + NE_ISR, 0x10);
		}

		if(ibits & 0x20)
		{
			dprintf("[NE2K ] counter overflow\n");
			outb(base + NE_ISR, 0x20);
		}
	}
}

void ne2k_start(unsigned short base, int num_irq)
{
    dprintf("[NE2K] found card io:%x, irq:%d", base, num_irq);

	// reset the card
	outb(base + NE_RESET, inb(base + NE_RESET));
	while((inb(base + NE_ISR) & 0x80) == 0);
	outb(base + NE_ISR, 0xff);

    outb(base+NE_CMD,    0x21); // reset

    outb(base+NE_DCR,    0x09); // normal operation, 16-bit transfer
    outb(base+NE_RCR,    0x20); // set to monitor to avoid receive
    outb(base+NE_TCR,    0x02); // and loopback just in case

    outb(base+NE_ISR,    0xFF); // clear interrupts
    outb(base+NE_IMR,    0);    // no interrupts for now

    // Now get the MAC from the PROM first
    outb(base+NE_RSAR0,  0);
    outb(base+NE_RSAR1,  0);

    outb(base+NE_RBCR0,  32); // read 32 bytes
    outb(base+NE_RBCR1,  0);

    outb(base+NE_CMD,    0x0a); // remote read + STOP (don't want traffic)

    unsigned short prom[16];
    int i;
    for(i = 0; i < 16; i++) {
        prom[i] = inw(base+NE_DATA);
    }

    // Set physical address on page 1
    dprintf(" [mac");
    outb(base+NE_CMD,    0x61);
    for(i = 0; i < 6; i++) {
        dprintf(":%2x", prom[i] & 0xff);
        outb(base+NE_PAR+i, prom[i] & 0xff);
    }
    dprintf("]\n");

    // Reset current (still on page1), leave one extra page to please
    // QEMU it doesn't support "send packet" anyway, so can't use that.
    // Also set our packet pointer..
    outb(base+NE_CURR,   PAGE_RX+1);

    // ok we should be more or less done, take the NIC into normal mode
    outb(base+NE_CMD,    0x21);

    // must set these here, if set above, stuff goes wrong if packet
    // arrives while we're initializing..
    outb(base+NE_PSTART, PAGE_RX);   // start
    outb(base+NE_BNDRY,  PAGE_RX);   // same as start
    outb(base+NE_PSTOP,  PAGE_STOP); // stop

    outb(base+NE_RCR,    0x06); // accept broadcast and runts
    outb(base+NE_TCR,    0x00); // default everything

	installed_cards[nInstalled].base = base;
	installed_cards[nInstalled].next_packet = PAGE_RX + 1;

    unsigned char ip[4] = { 192, 168, 1, 123 + nInstalled };
    dprintf("[NE2K ] becoming %d.%d.%d.%d\n", ip[0], ip[1], ip[2], ip[3]);

    for(i = 0; i < 6; i++) {
		installed_cards[nInstalled].macaddr[i] = prom[i] & 0xff;
    }

	nInstalled++;

	kInstallCustomIntrHandler(INTERRUPT_FROM_IRQ(num_irq), ne2k_irq_handler);
	dprintf( "[NE2K ] installed irq handler on irq %d\n", num_irq );

    outb(base+NE_ISR,    0xFF); // clear interrupts again (just in case)
    outb(base+NE_IMR,    0x3F); // enable all interrupts

    // start NIC operation
    outb(base+NE_CMD,    0x22);
}

int ne2k_send(uint32_t iface, const char* buff, int len)
{
    if(len > 0xffff)
	{
		dprintf("[NE2K ] over 64k packet send attempt\n");
		while( 1 );
	}
	struct ne2k_card card = installed_cards[iface];

    // disable interrupts to be safe.. receiver runs without
    // interrupts anyway so no need for fancier locking..
	asm volatile("cli");

    // set length and address for Remote DMA write
    outb(card.base + NE_RSAR0, 0);
    outb(card.base + NE_RSAR1, PAGE_TX);
    outb(card.base + NE_RBCR0, len > 64 ? len & 0xff : 64);
    outb(card.base + NE_RBCR1, len >> 8);

    outb(card.base + NE_CMD, 0x12); // WRITE + START

    unsigned short * data = (unsigned short*) buff;
    int i;
    for(i = 0; i + 1 < len; i += 2) {
        outw(card.base + NE_DATA, data[i/2]);
    }

    // odd byte..
    if(len & 1) {
        if(len < 64) {
            // technically buffer overflow, but known to be safe
            outw(card.base + NE_DATA, data[i/2] & 0xff);
            i += 2;
        } else {
            outb(card.base + NE_DATA, data[i/2] & 0xff);
            i ++;
        }
    }
    // do padding if necessary
    for(;i < 64; i += 2) outw(card.base + NE_DATA, 0);

    // wait until completion
    while(!(inb(card.base + NE_ISR) & 0x40)) iodelay();
    outb(card.base + NE_ISR, 0x40);

    // then tell the NIC to transmit that data
    outb(card.base + NE_TBCR0, len > 64 ? len & 0xff : 64);
    outb(card.base + NE_TBCR1, len >> 8);
    outb(card.base + NE_TPSR,  PAGE_TX);
    outb(card.base + NE_CMD, 0x26); // TX + START + NO DMA

	asm volatile("sti");

	// return the number of bytes sent
	return i;
}

void ne2k_recv(uint32_t card)
{
	uint32_t base = installed_cards[card].base;

	// first ACK the interrupt, so we get another if we spend
	// too much time fooling around here..
	outb(base + NE_ISR, 0x01);

	// then fetch the current counter..
	outb(base + NE_CMD, 0x61); // go to page1
	unsigned char current = inb(base + NE_CURR);
	outb(base + NE_CMD, 0x21); // back to page0

	// Now read packets until we hit 'current' which we just read.
	while(installed_cards[card].next_packet != current) {

		// Read the a few words from the beginning,
		// as we need length to know how much to read.
		outb(base + NE_RSAR0, 0);
		outb(base + NE_RSAR1, installed_cards[card].next_packet);
		outb(base + NE_RBCR0, 4);
		outb(base + NE_RBCR1, 0);
		outb(base + NE_CMD, 0x0A); // READ + START

		unsigned short status = inw(base + NE_DATA);
		unsigned short length = inw(base + NE_DATA);

		unsigned short * packet = (unsigned short*) malloc(length - 3);

		// Check for status, and set a new read for rest of the packet
		while(!(inb(base + NE_ISR) & 0x40)) iodelay();
		outb(base + NE_ISR, 0x40);

		outb(base + NE_RSAR0, 4);
		outb(base + NE_RSAR1, installed_cards[card].next_packet);
		outb(base + NE_RBCR0, (length-4) & 0xff);
		outb(base + NE_RBCR1, (length-4) >> 8);
		outb(base + NE_CMD, 0x0A); // READ + START

		int i, words = (length-3)/2;
		for(i = 0; i < words; ++i) {
			packet[i] = inw(base + NE_DATA);
		}
		// special case the odd packet length (not really necessary)
		if(length & 1) {
			packet[length-1] = inb(base + NE_DATA);
		}

		// Check for status again..
		while(!(inb(base + NE_ISR) & 0x40)) iodelay();
		outb(base + NE_ISR, 0x40);

		// Set next packet, and tell NIC new boundary..
		installed_cards[card].next_packet = status >> 8;
		outb(base + NE_BNDRY,
		(installed_cards[card].next_packet == PAGE_RX)
		? (PAGE_STOP - 1) : (installed_cards[card].next_packet - 1));

		// Device must be safe (and interrupts enabled) before dispatch
		// since we might call transmit code directly from this thread.

		// needs to go to the next layer - do so by linking to an eth
		// perhaps via an ioctl call?

		// free the packet
		free(packet);
	}

	outb(base + NE_IMR, 0x3F); // unmask the interrupts
}

void ne2k_init()
{
	// try to start the device
	//dprintf("[NE2K ] detecting card at %x/%d... ", ioaddr, irq);
	ne2k_start( ioaddr, irq );
	//dprintf("Done\n");
}

char* ne2k_getmac(uint32_t iface)
{
	return installed_cards[iface].macaddr;
}
